(function() {

var utils = extension.utils;
var messages = extension.messages;
var preferences = extension.preferences;
var sessions = extension.sessions;
var filters = extension.filters;
var storage = extension.storage;
var sync = extension.sync;
var ready = utils.event();
var frame = Object.create(null);
var frames = Object.create(null);
var currentTabId = -1;
var currentWindowId = -1;
var config = Object.create(null);
var icons = Object.create(null);
var BLOCK_FRAME = 'window.FlashControl&&FlashControl("block",{0})\n//@ sourceURL=injected';
var SEND_STATUS = 'window.FlashControl&&FlashControl("{0}",{1})\n//@ sourceURL=injected';
var CHECK_STATE = 'window.FlashControl&&FlashControl("validate")\n//@ sourceURL=injected';

var idle = {
    detect: function(n) {
        chrome.idle.onStateChanged.removeListener(idle.onStateChanged);
        if (n > 0) {
            chrome.idle.onStateChanged.addListener(idle.onStateChanged);
            chrome.idle.setDetectionInterval(Math.max(n, 60));
        }
    },
    onStateChanged: function(d) {
        if (d === 'idle') {
            console.log('[%s] %s', utils.time(), 'entering idle mode');
            var a = preferences.get('prefs.idletabs');
            chrome.tabs.query({url: '*://*/*'}, function(t) {
                for (var i = 0; i < t.length; i++) {
                    if (a && t[i].active)
                        continue;
                    blockIsolatedWorlds(t[i].id, true);
                    updateToolbarMenu(t[i].id);
                }
                updateContextMenu();
            });
        }
    }
};

var exports = {
    togglePage: function(tab) {
        blockIsolatedWorlds(tab.id, !frames[tab.id].blocked);
        updateToolbarMenu(tab.id);
        updateContextMenu(tab.id);
    },
    toggleSession: function(tab) {
        var url = frames[tab.id].url;
        var blocked = sessions.get(url);
        var block = preferences.get('prefs.defaultmode') ? !blocked : blocked;
        sessions.set(url, !blocked);
        chrome.tabs.query({url: utils.hostname(url, 1) + '/*'}, function(t) {
            for (var i = 0; i < t.length; i++) {
                blockIsolatedWorlds(t[i].id, block);
                updateToolbarMenu(t[i].id);
            }
            updateContextMenu();
        });
    },
    toggleWhitelist: function(tab) {
        var frame = frames[tab.id], url = frame.url;
        filters.updateWhitelist(url, utils.authority(url), frame.status !== 'allow');
        chrome.tabs.query({url: '*://' + utils.hostname(url) + '/*'}, function(t) {
            for (var i = 0; i < t.length; i++) {
                loadIsolatedWorlds(t[i]);
                updateToolbarMenu(t[i].id);
            }
            updateContextMenu();
        });
    },
    toggleBlacklist: function(tab) {
        var frame = frames[tab.id], url = frame.url;
        filters.updateBlacklist(url, utils.authority(url), frame.status !== 'deny');
        chrome.tabs.query({url: '*://' + utils.hostname(url) + '/*'}, function(t) {
            for (var i = 0; i < t.length; i++) {
                loadIsolatedWorlds(t[i]);
                updateToolbarMenu(t[i].id);
            }
            updateContextMenu();
        });
    },
    toggleExtension: function() {
        preferences.set('prefs.enabled', !preferences.get('prefs.enabled'));
        updateContextMenu();
    },
    getFrameInfo: function(tab) {
        var frame = frames[tab.id], copy = utils.copy(frame);
        copy.session = sessions.get(frame.url);
        return copy;
    },
    getSettings: function() {
        return utils.adapter(preferences, 'prefs.');
    },
    getFilters: function() {
        return utils.adapter(filters, 'data.');
    },
    getSync: function() {
        return utils.adapter(sync, 'sync.');
    },
    openInspectorTool: function(tab) {
        var id = tab.id;
        var path = chrome.runtime.getURL('inspector.html');
        chrome.tabs.query({url: path}, function(t) {
            var url = path + '#' + id;
            for (var i = 0; i < t.length; i++) {
                if (t[i].url === url) {
                    chrome.windows.update(t[i].windowId, {focused: true});
                    return;
                }
            }
            chrome.windows.create({
                width: 740,
                height: 380,
                type: 'popup',
                focused: true,
                url: url
            });
        });
    },
    openOptionsPage: function() {
        var path = chrome.runtime.getURL('options.html');
        chrome.tabs.query({url: path}, function(t) {
            if (t.length > 0)
                chrome.tabs.update(t[0].id, {active: true});
            else
                chrome.tabs.create({active: true, url: path});
        });
    },
    mergeAccountData: updateAccount
};

Object.defineProperties(frame, {
    toString: {
        value: function() {
            return JSON.stringify(this);
        }
    }
});

Object.defineProperty(frames, '-1', {
    value: Object.freeze(createFrame('destroy', 'chrome://null', false))
});

function runScript(i, x, c) {
    if (frames[i] !== frames[-1]) {
        var z = {allFrames: true};
        if (navigator.userAgent.match(/Chrome\/(\d+)/)[1] > 36)
            z.matchAboutBlank = true;
        if (c === 'css')
            chrome.tabs.insertCSS(i, utils.merge(z, x));
        else
            chrome.tabs.executeScript(i, utils.merge(z, x));
    }
}

function createFrame(s, u, b) {
    return Object.create(frame, {
        url: {
            value: u
        },
        blocked: {
            configurable: true,
            enumerable: true,
            writable: true,
            value: b
        },
        status: {
            enumerable: true,
            value: s
        },
        found: {
            writable: true,
            value: 0
        },
        icon: {
            value: document.createElement('canvas')
        }
    });
}

function loadFrame(t) {
    var f, u = t.url;
    if (!utils.permitted(u))
        f = frames[-1];
    else if (!preferences.get('prefs.enabled'))
        f = createFrame('destroy', u, false);
    else if (filters.matchWhitelist(u)) {
        f = createFrame('allow', u, false);
        f.preferences = config;
    }
    else if (filters.matchBlacklist(u))
        f = createFrame('deny', u, true);
    else {
        f = createFrame('capture', u, true);
        f.blocked = isTabBlocked(t);
        f.preferences = config;
        f.filterset = filters.matchSelective(u);
    }
    return frames[t.id] = f;
}

function getFrame(i) {
    return frames[i] || (frames[i] = createFrame('', '', false));
}

function updateIsolatedWorlds(t) {
    loadFrame(t);
    loadIsolatedWorlds(t);
}

function loadIsolatedWorlds(t) {
    var f = frames[t.id];
    runScript(t.id, {code: utils.format(SEND_STATUS, f.status, f)});
}

function blockIsolatedWorlds(i, b) {
    frames[i].blocked = b;
    runScript(i, {code: utils.format(BLOCK_FRAME, b)});
}

function isTabBlocked(t) {
    var b = preferences.get('prefs.tabfocus');
    if (b == 0) {
        var session_mode = sessions.get(t.url);
        return preferences.get('prefs.defaultmode') ? session_mode : !session_mode;
    }
    if (b & 0x01 && t.active) return false;
    if (b & 0x02 && !t.active) return true;
}

function onTabCreated(t) {
    frames[t.id] = frames[-1];
}

function onTabUpdated(i, c, t) {
    switch (c.status) {
    case 'loading':
        if (c.url && getFrame(i).url != c.url) {
            loadFrame(t);
            runScript(i, {code: CHECK_STATE, allFrames: false});
        }
        getFrame(i).found = 0;
        updateToolbarMenu(i);
        break;
    case 'complete':
        updateContextMenu(i);
        break;
    case 'replaced':
        updateIsolatedWorlds(t);
        updateToolbarMenu(i);
        updateContextMenu(i);
        break;
    }
}

function onTabReplaced(a, b) {
    onTabRemoved(b, {});
    chrome.tabs.get(a, function(t) {
        onTabUpdated(t.id, {status: 'replaced'}, t);
    });
}

function onTabRemoved(i, d) {
    delete frames[i];
    if (i == currentTabId)
        currentTabId = -1;
}

function onTabActivated(d) {
    var b = preferences.get('prefs.tabfocus');
    if (b & 0x01) {
        blockIsolatedWorlds(d.tabId, false);
        updateToolbarMenu(d.tabId);
    }
    if (b & 0x02) {
        blockIsolatedWorlds(currentTabId, true);
        updateToolbarMenu(currentTabId);
    }
    currentTabId = d.tabId;
    updateContextMenu(d.tabId);
}

function onWindowFocused(i) {
    if (i != chrome.windows.WINDOW_ID_NONE) {
        currentWindowId = i;
        updateContextMenu();
    }
}

function onStorageChanged(o, a) {
    if (a === 'sync')
        sync.merge(o);
}

function onRuntimeMessage(m, s, c) {
    if (s.tab) {
        switch (m.type) {
        case 'status':
            c(frames[s.tab.id]);
            break;
        case 'audit':
            if (frames[s.tab.id]) {
                frames[s.tab.id].found += !m.info.indexed;
                updateToolbarMenu(s.tab.id);
            }
            break;
        case 'update':
            loadIsolatedWorlds(s.tab);
            updateContextMenu(s.tab.id);
            break;
        }
    }
}

function onRuntimeInstalled() {
    chrome.tabs.query({url: '*://*/*'}, function(t) {
        for (var i = 0; i < t.length; i++) {
            runScript(t[i].id, {file: 'panel.css'}, 'css');
            runScript(t[i].id, {file: 'content.js'});
        }
        extension.exports.openOptionsPage();
    });
}

function onRuntimeUpdated(v) {
}

function onSettingsChanged(c) {
    var data = c.values;
    for (var key in data) {
        switch (key) {
        case 'prefs.enabled':
            chrome.tabs.query({url: '*://*/*'}, function(t) {
                for (var i = 0; i < t.length; i++) {
                    var n = getFrame(t[i].id).found;
                    updateIsolatedWorlds(t[i]);
                    getFrame(t[i].id).found = n;
                    updateToolbarMenu(t[i].id);
                }
                loadContextMenu();
            });
            break;
        case 'prefs.omniicon':
        case 'prefs.omnialways':
            chrome.tabs.query({url: '*://*/*'}, function(t) {
                for (var i = 0; i < t.length; i++)
                    updateToolbarMenu(t[i].id);
            });
            break;
        case 'prefs.contextmenu':
            loadContextMenu();
            break;
        case 'prefs.plugincounter':
            chrome.tabs.query({url: '*://*/*'}, function(t) {
                for (var i = 0; i < t.length; i++)
                    updateToolbarMenu(t[i].id);
            });
            break;
        case 'prefs.toolbar':
        case 'prefs.toolbarposition':
        case 'prefs.flashborder':
        case 'prefs.panelcolor':
        case 'prefs.showicon':
        case 'prefs.panelimage':
        case 'prefs.paneltooltip':
        case 'prefs.panelclick':
        case 'prefs.flashquality':
            updateIsolatedWorldConfig();
            break;
        case 'prefs.idleseconds':
            idle.detect(data[key]);
            break;
        case 'data.patternlistA':
        case 'data.patternlistB':
        case 'data.patternlistC':
        case 'data.patternlistD':
        case 'data.patternlistE':
        case 'data.patternlistF':
        case 'prefs.defaultmode':
            chrome.tabs.query({url: '*://*/*'}, function(t) {
                for (var i = 0; i < t.length; i++) {
                    var n = getFrame(t[i].id).found;
                    loadFrame(t[i]).found = n;
                }
            });
            break;
        case 'sync.enabled':
        case 'sync.settings':
        case 'sync.filters':
            updateAccount();
            break;
        }
    }
}

function onContextMenu(i, t) {
    extension.exports[i.menuItemId](t);
}

function addContextmenuItem(x) {
    chrome.contextMenus.create(utils.merge({
        contexts: ['page', 'frame'],
        onclick: onContextMenu
    }, x));
}

function loadContextMenu() {
    chrome.contextMenus.removeAll(function() {
        if (!preferences.get('prefs.contextmenu'))
            return;
        addContextmenuItem({id: 'togglePage', title: 'Allow Flash on this page', type: 'checkbox'});
        addContextmenuItem({id: 'toggleWhitelist', title: 'Whitelist', type: 'checkbox'});
        addContextmenuItem({type: 'separator'});
        addContextmenuItem({id: 'openInspectorTool', title: 'Inspect this tab'});
        addContextmenuItem({type: 'separator'});
        addContextmenuItem({id: 'toggleExtension', title: 'Disable FlashControl', type: 'checkbox'});
        addContextmenuItem({id: 'openOptionsPage', title: 'Settings'});
        updateContextMenu();
    });
}

function updateContextMenu(i) {
    chrome.tabs.query({
        active: true,
        lastFocusedWindow: true,
        windowId: currentWindowId
    }, function(t) {
        if (t.length === 0 || (i != null && t[0].id != i))
            return;
        var f = frames[t[0].id];
        var x = f === frames[-1];
        var e = !!preferences.get('prefs.enabled');
        var v = e && !x;
        var b = f.status === 'deny';
        var w = f.status === 'allow';
        var h = utils.authority(f.url) || f.url;
        chrome.contextMenus.update('togglePage', {
            enabled: v && !b && !w,
            checked: v && !f.blocked
        });
        chrome.contextMenus.update('toggleWhitelist', {
            enabled: v && !b,
            checked: !x && w,
            title: 'Whitelist ' + (h !== 'null' ? h : '...')
        });
        chrome.contextMenus.update('openInspectorTool', {
            enabled: !x
        });
        chrome.contextMenus.update('toggleExtension', {
            checked: !e
        });
    });
}

function generateIconImage(f) {
    var s = f.status === 'capture' && !f.blocked ? 'allow' : f.status;
    var n = preferences.get('prefs.plugincounter') && f.found > 0 ? f.found : '';
    return {
        19: utils.icon(f.icon, {size: 19, image: icons[s + 19], text: n, font: 10}),
        38: utils.icon(f.icon, {size: 38, image: icons[s + 38], text: n, font: 20})
    };
}

function updateToolbarMenu(i) {
    var f = frames[i];
    if (f === frames[-1] || !preferences.get('prefs.omniicon')) {
        chrome.pageAction.hide(i);
        return;
    }
    var e = preferences.get('prefs.enabled');
    if (!e || preferences.get('prefs.omnialways') || f.found) {
        chrome.pageAction.setIcon({
            tabId: i,
            imageData: generateIconImage(f)
        });
        chrome.pageAction.show(i);
    }
    else
        chrome.pageAction.hide(i);
}

function updateIsolatedWorldConfig() {
    config.toolbar = preferences.get('prefs.toolbar');
    config.toolbarposition = preferences.get('prefs.toolbarposition');
    config.paneltooltip = preferences.get('prefs.paneltooltip');
    config.flashquality =
        ['', 'low', 'autolow', 'autohigh', 'medium', 'high', 'best'][preferences.get('prefs.flashquality')];
    config.placeholder = createPlaceholder(preferences.getChanges());
    config.panelclick = preferences.get('prefs.panelclick');
}

function updateAccount() {
    sync.pull(utils.merge(filters.get(), preferences.get()));
    sync.push(utils.merge(filters.getChanges(), preferences.getChanges()));
}

function createPlaceholder(o) {
    var css = {panel: '', toolbar: ''};
    if ('prefs.panelcolor' in o) {
        var color = o['prefs.panelcolor'];
        css.panel += utils.format('background-color:rgba({0},{1},{2},{3});',
            color[0], color[1], color[2], color[3].toFixed(1));
    }
    if (o['prefs.showicon'] === 0)
        css.panel += 'background-image:none;';
    else if ('prefs.panelimage' in o)
        css.panel += utils.format('background-image:url({0}/{1});',
            chrome.runtime.getURL('graph'), o['prefs.panelimage']);
    if ('prefs.flashborder' in o) {
        var color = o['prefs.panelcolor'] || ['0', '0', '0'];
        css.panel += utils.format('outline:rgb({0},{1},{2}) solid 1px;',
            color[0], color[1], color[2]);
    }
    switch (o['prefs.toolbarposition']) {
        case 3: css.toolbar += 'right:0;bottom:0;'; break;
        case 2: css.toolbar += 'left:0;bottom:0;'; break;
        case 1: css.toolbar += 'top:0;right:0;'; break;
    }
    return css;
}

chrome.runtime.onInstalled.addListener(function(d) {
    ready.addListener(function() {
        if (d.reason === 'install')
            messages.send('install-runtime');
        if (d.reason === 'update')
            messages.send('update-runtime');
    });
});

utils.series([
    function(pass) {
        utils.process([
            function(pass) {
                storage.local.get(preferences.get(), function(o) {
                    preferences.set(o);
                    pass();
                });
            },
            function(pass) {
                storage.local.get(filters.get(), function(o) {
                    filters.set(o);
                    pass();
                });
            },
            function(pass) {
                storage.local.get(sync.get(), function(o) {
                    sync.set(o);
                    pass();
                });
            }],
            pass
        );
    },
    function(pass) {
        function load(p, n) {
            return function(pass) {
                var o = new Image();
                o.onload = pass;
                o.src = 'graph/' + p + '.png';
                icons[p] = o;
            }
        }
        utils.process([
            load('capture19', 19),
            load('allow19', 19),
            load('deny19', 19),
            load('destroy19', 19),
            load('capture38', 38),
            load('allow38', 38),
            load('deny38', 38),
            load('destroy38', 38)],
            pass
        );
    },
    function(pass) {
        utils.process([
            function(pass) {
                chrome.tabs.query({}, function(t) {
                    for (var i = 0; i < t.length; i++) {
                        updateIsolatedWorlds(t[i]);
                        updateToolbarMenu(t[i].id);
                    }
                    pass();
                });
            },
            function(pass) {
                chrome.tabs.query({lastFocusedWindow: true, active: true}, function(t) {
                    if (t.length > 0) {
                        currentWindowId = t[0].windowId;
                        currentTabId = t[0].id;
                    }
                    pass();
                });
            }],
            pass
        );
    }],
    function() {
        chrome.tabs.onCreated.addListener(onTabCreated);
        chrome.tabs.onUpdated.addListener(onTabUpdated);
        chrome.tabs.onReplaced.addListener(onTabReplaced);
        chrome.tabs.onRemoved.addListener(onTabRemoved);
        chrome.tabs.onActivated.addListener(onTabActivated);
        chrome.windows.onFocusChanged.addListener(onWindowFocused);
        chrome.storage.onChanged.addListener(onStorageChanged);
        chrome.runtime.onMessage.addListener(onRuntimeMessage);
        storage.bind();
        preferences.bind();
        filters.bind();
        sync.bind();
        messages.listen({
            'change-settings': onSettingsChanged,
            'install-runtime': onRuntimeInstalled,
            'update-runtime': onRuntimeUpdated
        });
        loadContextMenu();
        updateIsolatedWorldConfig();
        updateAccount();
        idle.detect(preferences.get('prefs.idleseconds'));
        extension.exports = exports;
        ready.fire();
    }
);

})();
